/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.core.components;

import haxe.xml.Check;
import com.chasekernan.hxnova.utils.Rectangle;
import com.chasekernan.hxnova.utils.Vector;

/**
    A hull is made up of these.
**/
typedef HullPart = {
    var type : ComponentTypeSet;
    var amount : Int;
    var position : Rectangle;
}

/**
    Allows for multiple, arbitrarily shaped storage spaces.
**/
typedef StorageSpace = {
    var amount : Int;
    var position : Rectangle;
}

/**
    The base hull for all ships/starbases.
**/
class Hull extends Component {
    
    public var parts : Array<HullPart>;
    public var storage : Array<StorageSpace>;
    public var backgroundPic : String;
    
    public var fuelCapacity(default, setFC) : Int;
    private function setFC(fc : Int) {if(fc < 1) throw "FuelCapacity must be greater than 0."; fuelCapacity = fc; return fuelCapacity;}
    
    public var baseArmor(default, setBA) : Int;
    private function setBA(ba : Int) {if(ba < 0) throw "BaseArmor must be atleast 0."; baseArmor = ba; return baseArmor;}
    
    public var baseInitiative(default, setBI) : Int;
    private function setBI(bi : Int) {if(bi < 0) throw "BaseInitiative must be atleast 0."; baseInitiative = bi; return baseInitiative;}
    
    public function new(name : String, ?fuelCapacity : Int, ?baseArmor : Int, 
            ?baseInitiative : Int) {
        super(name);
        parts = new Array<HullPart>();
        storage = new Array<StorageSpace>();
        
        backgroundPic = "";
        
        setFC(if(fuelCapacity == null) 1 else fuelCapacity);
        setBA(this.baseArmor = if(baseArmor == null) 0 else baseArmor);
        setBI(this.baseInitiative = if(baseInitiative == null) 0 else baseInitiative);
    }
    
    /**
        Adds a hull part to the hull.
        Will throw the same erros as [addPartObject].
    **/
    public function addPart(type : ComponentTypeSet, amount : Int, 
            position : Rectangle) {
        addPartObject( { type : type, amount : amount, position : position } );
    }
    
    /**
        Adds a part to the hull.
        Will throw an error if the number of parts is less than one or if the 
        part type is == null.
    **/
    public function addPartObject(part : HullPart) : Void {
        if(part.amount < 1) 
            throw "Amount of a part must be greater than or equal to 1.";
        if(part.type == null) throw "Cannot add a null type to the parts list.";
        parts.push(part);
    }
    
    /**
        Adds storage space to the hull.
        Throws the same errors as [addStorageObject].
    **/
    public function addStorageSpace(amount : Int, position : Rectangle) {
        addStorageObject( { amount : amount, position : position } );
    }
    
    /**
        Adds storage space to the hull.
        Will throw an error if the amount of storage is less than 1.
    **/
    public function addStorageObject(s : StorageSpace) : Void {
        if(s.amount <= 1) 
            throw "Amount of storage must be greater than or equal to 1.";
        storage.push(s);
    }
    
    /**
        Returns the total amount of storage available to the hull (excluding 
        fuel storage).
    **/
    public function getTotalStorageSpace() : Int {
        var t = 0;
        for(i in storage) t += i.amount;
        return t;
    }
    
    /**
        Returns true if the hull as any storage space.
    **/
    public function hasStorage() : Bool {
        return getTotalStorageSpace() > 0;
    }
    
    /**
        Returns true if the design is a starbase, i.e. if there is no engine 
        slot then its a starbase.
    **/
    public function isStarbase() : Bool {
        var foundEngine = false;
        for(i in parts) {
            if(i.type.hasType(Engine)) {
                if(foundEngine) return false; 
                else foundEngine = true;
            }
        }
        return !foundEngine;
    }
    
    /**
        Finds the first hull part that contains that point.
        Returns null if there is no part there.
    **/
    public function getPart(pos : Vector) : HullPart {
        for(i in parts) {
            if(pos.x >= i.position.topLeft.x && pos.y >= i.position.topLeft.y
                    && pos.x <= (i.position.topLeft.x + i.position.dimensions.x)
                    && pos.y <= (i.position.topLeft.y + i.position.dimensions.y)
                    ) return i;
        }
        
        return null;
    }
    
    /**
     * Gives the slot number of a given position.
     * @return -1 if no such slot
     */
    public function getSlotNumber(pos : Vector) : Int {
        for(i in 0...parts.length) {
            if(pos.x >= parts[i].position.topLeft.x && pos.y >= parts[i].position.topLeft.y
                && pos.x <= (parts[i].position.topLeft.x + parts[i].position.dimensions.x)
                && pos.y <= (parts[i].position.topLeft.y + parts[i].position.dimensions.y)) return i;
        }
        
        return -1;
    }
    
    /**
        Returns true if the hull has a weapons slot
    **/
    public function hasWeapons() : Bool {
        try {
            var weap = ComponentSet.getComponentTypeSetByName('Weapon')
                    .getNames();
            for(i in parts) {
                for(j in weap) {
                    if(i.type.hasTypeByName(j)) return true;
                }
            }
        } catch(e : Dynamic) {
            throw 'Weapon component type set needs to be defined.';
        }
        
        return false;
    }
    
    public static function getXmlRules() : Rule {
        var h = new Hull("");
        
        var children : Array<Rule> = h.getChildren();
        children.push(RNode('parts', [], RMulti(RNode('part', [Att('amount'), 
                Att('type'), Att('x1'), Att('y1'), Att('x2'), Att('y2')]), 
                false)));
        children.push(RNode('storage_spaces', [], RMulti(RNode('storage', 
                [Att('amount'), Att('x1'), Att('y1'), Att('x2'), Att('y2')]), 
                false)));
        return RNode(h.getXmlTitle(), h.getAtt(h).concat([Att('is_starbase')]),
					 RList(children, false));
    }
    
    public static function writeToXml(h : Hull) : Xml {
        var xml = Xml.parse('<' + h.getXmlTitle() + '></' + h.getXmlTitle() 
                + '>').firstChild();
        
        xml.set('is_starbase', Std.string(h.isStarbase()));
		xml.set("background_pic", h.backgroundPic);
        
        var pchild = Xml.createElement('parts');
        for(i in h.parts) {
            var part = Xml.createElement('part');
            part.set('amount', Std.string(i.amount));
            part.set('type', i.type.name);
            part.set('x1', Std.string(i.position.topLeft.x));
            part.set('y1', Std.string(i.position.topLeft.y));
            part.set('x2', Std.string(i.position.topLeft.x + 
                    i.position.dimensions.x));
            part.set('y2', Std.string(i.position.topLeft.y + 
                    i.position.dimensions.y));
            pchild.addChild(part);
        }
        xml.addChild(pchild);
        
        var schild = Xml.createElement('storage_spaces');
        for(i in h.storage) {
            var s = Xml.createElement('storage');
            s.set('amount', Std.string(i.amount));
            s.set('x1', Std.string(i.position.topLeft.x));
            s.set('y1', Std.string(i.position.topLeft.y));
            s.set('x2', Std.string(i.position.topLeft.x + 
                    i.position.dimensions.x));
            s.set('y2', Std.string(i.position.topLeft.y + 
                    i.position.dimensions.y));
            schild.addChild(s);
        }
        xml.addChild(schild);
        
        h.setAttribsAndChildren(xml, h);
        
        //Check.checkNode(xml, getXmlRules());
        
        return xml;
    }
    
    public static function readFromXml(xml : Xml) : Hull {
        try {
            //Check.checkNode(xml, getXmlRules());
            
            var h = new Hull("");
            
            h.parts = new Array<HullPart>();
            h.storage = new Array<StorageSpace>();
            
            h.generateFromComponentXml(xml, h);
			
			h.backgroundPic = xml.get("background_pic");
            
            for(i in xml.elementsNamed('parts').next().elements()) {
                var part = 
                    {amount : 0, type : null, position : new Rectangle()};
                
                part.amount = Std.parseInt(i.get('amount'));
                part.type = ComponentSet.getComponentTypeSetByName(i.get('type'));
                
                part.position.topLeft.x = Std.parseInt(i.get('x1'));
                part.position.topLeft.y = Std.parseInt(i.get('y1'));
                
                part.position.dimensions.x = Std.parseInt(i.get('x2')) - 
                    part.position.topLeft.x;
                part.position.dimensions.y = Std.parseInt(i.get('y2')) - 
                    part.position.topLeft.y;
                
                h.addPartObject(part);
            }
            
            for(i in xml.elementsNamed('storage_spaces').next().elements()) {
                var s = {amount : 0, position : new Rectangle()};
                
                s.amount = Std.parseInt(i.get('amount'));
                
                s.position.topLeft.x = Std.parseInt(i.get('x1'));
                s.position.topLeft.y = Std.parseInt(i.get('y1'));
                s.position.dimensions.x = Std.parseInt(i.get('x2')) 
                        - s.position.topLeft.x;
                s.position.dimensions.y = Std.parseInt(i.get('y2')) 
                        - s.position.topLeft.y;
                
                h.addStorageObject(s);
            }
            
            return h;
        } catch (e : Dynamic) {
            throw "Error while reading from a hull xml file : " + e;
        }
    }
}